package lc.util;

import com.jme.math.Matrix3f;
import com.jme.math.Matrix4f;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f;
import com.jme.scene.Spatial;

/**
 * Miscellaneous transformation math helpers that are not part of the
 * regular jMonkeyEngine.
 */
public final class Transform {
	/**
	 * Assumes that the Spatial s has its world vectors updated.
	 * @param s A Spatial to get the world transform for.
	 * @return A Matrix4f representing the world transform.
	 */
	public static Matrix4f getWorld(Spatial s) {
		return createTranslation(s.getWorldTranslation())
			.mult(createRotation(s.getWorldRotation())
					.mult(createScale(s.getWorldScale())));
	}
	/**
	 * @param s A Spatial to get the local transform for.
	 * @return A Matrix4f representing the local transform.
	 */
	public static Matrix4f getLocal(Spatial s) {
		return createTranslation(s.getLocalTranslation())
			.mult(createRotation(s.getLocalRotation())
					.mult(createScale(s.getLocalScale())));
	}
	/**
	 * Update the world transform for a Spatial (based on its current parent
	 * transform, which is assumed to be up to date).
	 * @param rotScale The rotation and scale, expressed as a Matrix3f.
	 * @param xlate The translation.
	 * @param s The spatial that is to be affected. It's OK if it doesn't have a parent,
	 * in that case, this is the same as putLocal().
	 */
	public static void putWorld(Matrix3f rotScale, Vector3f xlate, Spatial s) {
		Matrix4f world = new Matrix4f(rotScale.m00, rotScale.m01, rotScale.m01, xlate.x,
				rotScale.m10, rotScale.m11, rotScale.m12, xlate.y,
				rotScale.m20, rotScale.m21, rotScale.m22, xlate.z,
				0, 0, 0, 1);
		putWorld(world, s);
	}
	/**
	 * Update the world transform for a Spatial (based on its current parent
	 * transform, which is assumed to be up to date).
	 * @param m The transform matrix to apply to the spatial.
	 * @param s The spatial to affect. It's OK to not have a parent.
	 */
	public static void putWorld(Matrix4f m, Spatial s) {
		Spatial p = s.getParent();
		if (p != null) {
			Matrix4f tmp = getWorld(p).invert();
			m = tmp.mult(m);
		}
		putLocal(m, s);
	}
	/**
	 * Update the local transform of a Spatial.
	 * @param rotScale The rotation and scale, expressed as a Matrix3f.
	 * @param xlate The translation.
	 * @param s The Spatial to affect.
	 */
	public static void putLocal(Matrix3f rotScale, Vector3f xlate, Spatial s) {
		Matrix4f local = new Matrix4f(rotScale.m00, rotScale.m01, rotScale.m01, xlate.x,
				rotScale.m10, rotScale.m11, rotScale.m12, xlate.y,
				rotScale.m20, rotScale.m21, rotScale.m22, xlate.z,
				0, 0, 0, 1);
		putLocal(local, s);
	}
	/**
	 * Update the local transform of a Spatial.
	 * @param m The transform matrix to apply.
	 * @param s The Spatial to affect.
	 */
	public static void putLocal(Matrix4f m, Spatial s) {
		Vector3f translation = new Vector3f();
		Vector3f scale = new Vector3f();
		Quaternion orientation = new Quaternion();
		decompose(m, translation, scale, orientation);
		s.setLocalTranslation(translation);
		s.setLocalScale(scale);
		s.setLocalRotation(orientation);
	}
	/**
	 * Create a Matrix4f representing a given scaling operation.
	 * @param s The scaling, per axis.
	 * @return The created scaling matrix.
	 */
	public static Matrix4f createScale(Vector3f s) {
		return new Matrix4f(s.x, 0, 0, 0, 0, s.y, 0, 0, 0, 0, s.z, 0, 0, 0, 0, 1);
	}
	/**
	 * Create a Matrix4f representing a given scaling operation.
	 * @param x The x axis scale.
	 * @param y The y axis scale.
	 * @param z The z axis scale.
	 * @return The created scaling matrix.
	 */
	public static Matrix4f createScale(float x, float y, float z) {
		return new Matrix4f(x, 0, 0, 0, 0, y, 0, 0, 0, 0, z, 0, 0, 0, 0, 1);
	}
	/**
	 * Create a Matrix4f representing a given scaling operation.
	 * @param s The scaling, as a global scaler.
	 * @return The created scaling matrix.
	 */
	public static Matrix4f createScale(float s) {
		return new Matrix4f(s, 0, 0, 0, 0, s, 0, 0, 0, 0, s, 0, 0, 0, 0, 1);
	}
	/**
	 * Create a rotation matrix, given a quaternion.
	 * @param q The quaternion to represent as a matrix.
	 * @return The created rotation matrix.
	 */
	public static Matrix4f createRotation(Quaternion q) {
		Matrix4f m = new Matrix4f();
		q.toRotationMatrix(m);
		m.m33 = 1;
		return m;
	}
	/**
	 * Create a matrix representing a specific translation.
	 * @param v The translation to represent.
	 * @return The matrix representing the translation.
	 */
	public static Matrix4f createTranslation(Vector3f v) {
		return new Matrix4f(1, 0, 0, v.x, 0, 1, 0, v.y, 0, 0, 1, v.z, 0, 0, 0, 1);
	}
	/**
	 * Create a matrix representing a specific translation.
	 * @param x Translation.
	 * @param y Translation.
	 * @param z Translation.
	 * @return The matrix representing the translation.
	 */
	public static Matrix4f createTranslation(float x, float y, float z) {
		return new Matrix4f(1, 0, 0, x, 0, 1, 0, y, 0, 0, 1, z, 0, 0, 0, 1);
	}
	/**
	 * Given a Matrix4f, extract a translation, scale and rotation which will re-create
	 * that matrix, assuming the matrix contains and orthogonal basis (no skew).
	 * @param m The matrix to decompose.
	 * @param translation The translation part of the matrix.
	 * @param scale The scaling part of the matrix.
	 * @param orientation The rotation part of the matrix.
	 */
	public static void decompose(Matrix4f m, Vector3f translation, Vector3f scale, Quaternion orientation) {
		translation.x = m.m03;
		translation.y = m.m13;
		translation.z = m.m23;
		Matrix3f r = new Matrix3f();
		m.toRotationMatrix(r);
		scale.x = r.getColumn(0).length();
		scale.y = r.getColumn(1).length();
		scale.z = r.getColumn(2).length();
		float l = 1.0f / scale.x;
		r.m00 *= l; r.m10 *= l; r.m20 *= l;
		l = 1.0f / scale.y;
		r.m01 *= l; r.m11 *= l; r.m21 *= l;
		l = 1.0f / scale.z;
		r.m02 *= l; r.m12 *= l; r.m22 *= l;
		orientation.fromRotationMatrix(r);
	}
}
